#pragma once

#include <assert.h>
#include <stdio.h>
#include <map>
#include <string>

#include "Buffer.h"

using namespace std;

class HttpRequest {
   public:
    enum Method { kInvalid, kGet, kPost, kHead, kPut, kDelete };
    enum Version { kUnknown, kHttp10, kHttp11 };

    HttpRequest(bool isclose = false) : method_(kInvalid), version_(kUnknown), closeConnection_(isclose) {}

    void setVersion(Version v) { version_ = v; }

    Version getVersion() const { return version_; }

    bool setMethod(const char *start, const char *end) {
        assert(method_ == kInvalid);
        string m(start, end);
        if (m == "GET") {
            method_ = kGet;
        } else if (m == "POST") {
            method_ = kPost;
        } else if (m == "HEAD") {
            method_ = kHead;
        } else if (m == "PUT") {
            method_ = kPut;
        } else if (m == "DELETE") {
            method_ = kDelete;
        } else {
            method_ = kInvalid;
        }
        return method_ != kInvalid;
    }

    Method method() const { return method_; }

    const char *methodString() const {
        const char *result = "UNKNOWN";
        switch (method_) {
            case kGet:
                result = "GET";
                break;
            case kPost:
                result = "POST";
                break;
            case kHead:
                result = "HEAD";
                break;
            case kPut:
                result = "PUT";
                break;
            case kDelete:
                result = "DELETE";
                break;
            default:
                break;
        }
        return result;
    }

    void setPath(const char *start, const char *end) { path_.assign(start, end); }

    const string &path() const { return path_; }

    void setQuery(const char *start, const char *end) { query_.assign(start, end); }

    void setBody(const char *start, const char *end) { body_.assign(start, end); }

    const string &body() const { return body_; }

    const string &query() const { return query_; }

    std::map<string, string> &querys() { return querys_; }

    void addHeader(const char *start, const char *colon, const char *end) {
        string field(start, colon);
        ++colon;
        while (colon < end && isspace(*colon)) {
            ++colon;
        }
        string value(colon, end);
        while (!value.empty() && isspace(value[value.size() - 1])) {
            value.resize(value.size() - 1);
        }
        headers_[field] = value;
    }

    string getHeader(const string &field) const {
        string result;
        std::map<string, string>::const_iterator it = headers_.find(field);
        if (it != headers_.end()) {
            result = it->second;
        }
        return result;
    }

    void addQuery(const std::string &parm, const std::string &value) { querys_.insert({parm, value}); }

    string getQuery(const string &field) const {
        string result;
        std::map<string, string>::const_iterator it = querys_.find(field);
        if (it != headers_.end()) {
            result = it->second;
        }
        return result;
    }
    const std::map<string, string> &headers() const { return headers_; }

    void swap(HttpRequest &that) {
        std::swap(method_, that.method_);
        std::swap(version_, that.version_);
        path_.swap(that.path_);
        query_.swap(that.query_);
        headers_.swap(that.headers_);
    }

    void appendToBuffer(Buffer* output) const {
        output->append(methodString());
        output->append(" ");
        output->append(path_);
        output->append("?");
        for (const auto& query : querys_) {
            output->append(query.first);
            output->append("=");
            output->append(query.second);
            output->append("&");
        }
        output->append(" ");
        output->append("HTTP/1.1");
        output->append("\r\n");

        if (closeConnection_) {
            output->append("Connection: close\r\n");
        } else {
            output->append("Connection: Keep-Alive\r\n");
        }

        for (const auto& header : headers_) {
            output->append(header.first);
            output->append(": ");
            output->append(header.second);
            output->append("\r\n");
        }

        output->append("\r\n");
        output->append(body_);
    }

   private:
    Method method_;
    Version version_;
    string path_;
    string query_;
    string body_;
    bool closeConnection_;
    std::map<string, string> querys_;
    std::map<string, string> headers_;
};
